#include <test/jtx.h>
#include <test/jtx/TrustedPublisherServer.h>

#include <xrpld/app/main/BasicApp.h>
#include <xrpld/app/misc/ValidatorSite.h>
#include <xrpld/core/ConfigSections.h>

#include <xrpl/beast/unit_test.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/jss.h>

#include <set>

namespace ripple {

namespace test {

class ValidatorRPC_test : public beast::unit_test::suite
{
    using Validator = TrustedPublisherServer::Validator;

public:
    void
    testPrivileges()
    {
        using namespace test::jtx;

        for (bool const isAdmin : {true, false})
        {
            for (std::string cmd : {"validators", "validator_list_sites"})
            {
                Env env{*this, isAdmin ? envconfig() : envconfig(no_admin)};
                env.set_retries(isAdmin ? 5 : 0);
                auto const jrr = env.rpc(cmd)[jss::result];
                if (isAdmin)
                {
                    BEAST_EXPECT(!jrr.isMember(jss::error));
                    BEAST_EXPECT(jrr[jss::status] == "success");
                }
                else
                {
                    // The current HTTP/S ServerHandler returns an HTTP 403
                    // error code here rather than a noPermission JSON error.
                    // The JSONRPCClient just eats that error and returns null
                    // result.
                    BEAST_EXPECT(jrr.isNull());
                }
            }

            {
                Env env{*this, isAdmin ? envconfig() : envconfig(no_admin)};
                auto const jrr = env.rpc("server_info")[jss::result];
                BEAST_EXPECT(jrr[jss::status] == "success");
                BEAST_EXPECT(
                    jrr[jss::info].isMember(jss::validator_list) == isAdmin);
            }

            {
                Env env{*this, isAdmin ? envconfig() : envconfig(no_admin)};
                auto const jrr = env.rpc("server_state")[jss::result];
                BEAST_EXPECT(jrr[jss::status] == "success");
                BEAST_EXPECT(
                    jrr[jss::state].isMember(jss::validator_list_expires) ==
                    isAdmin);
            }
        }
    }

    void
    testStaticUNL()
    {
        using namespace test::jtx;

        std::set<std::string> const keys = {
            "n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7",
            "n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj"};
        Env env{
            *this,
            envconfig([&keys](std::unique_ptr<Config> cfg) {
                for (auto const& key : keys)
                    cfg->section(SECTION_VALIDATORS).append(key);
                return cfg;
            }),
        };

        // Server info reports maximum expiration since not dynamic
        {
            auto const jrr = env.rpc("server_info")[jss::result];
            BEAST_EXPECT(
                jrr[jss::info][jss::validator_list][jss::expiration] ==
                "never");
        }
        {
            auto const jrr = env.rpc("server_state")[jss::result];
            BEAST_EXPECT(
                jrr[jss::state][jss::validator_list_expires].asUInt() ==
                NetClock::time_point::max().time_since_epoch().count());
        }
        // All our keys are in the response
        {
            auto const jrr = env.rpc("validators")[jss::result];
            BEAST_EXPECT(jrr[jss::validator_list][jss::expiration] == "never");
            BEAST_EXPECT(jrr[jss::validation_quorum].asUInt() == keys.size());
            BEAST_EXPECT(
                jrr[jss::trusted_validator_keys].size() == keys.size());
            BEAST_EXPECT(jrr[jss::publisher_lists].size() == 0);
            BEAST_EXPECT(jrr[jss::local_static_keys].size() == keys.size());
            for (auto const& jKey : jrr[jss::local_static_keys])
            {
                BEAST_EXPECT(keys.count(jKey.asString()) == 1);
            }
            BEAST_EXPECT(jrr[jss::signing_keys].size() == 0);
        }
        // No validator sites configured
        {
            auto const jrr = env.rpc("validator_list_sites")[jss::result];
            BEAST_EXPECT(jrr[jss::validator_sites].size() == 0);
        }
        // Negative UNL empty
        {
            auto const jrr = env.rpc("validators")[jss::result];
            BEAST_EXPECT(jrr[jss::NegativeUNL].isNull());
        }
        // Negative UNL update
        {
            hash_set<PublicKey> disabledKeys;
            auto k1 = randomKeyPair(KeyType::ed25519).first;
            auto k2 = randomKeyPair(KeyType::ed25519).first;
            disabledKeys.insert(k1);
            disabledKeys.insert(k2);
            env.app().validators().setNegativeUNL(disabledKeys);

            auto const jrr = env.rpc("validators")[jss::result];
            auto& jrrnUnl = jrr[jss::NegativeUNL];
            auto jrrnUnlSize = jrrnUnl.size();
            BEAST_EXPECT(jrrnUnlSize == 2);
            for (std::uint32_t x = 0; x < jrrnUnlSize; ++x)
            {
                auto parsedKey = parseBase58<PublicKey>(
                    TokenType::NodePublic, jrrnUnl[x].asString());
                BEAST_EXPECT(parsedKey);
                if (parsedKey)
                    BEAST_EXPECT(
                        disabledKeys.find(*parsedKey) != disabledKeys.end());
            }

            disabledKeys.clear();
            env.app().validators().setNegativeUNL(disabledKeys);
            auto const jrrUpdated = env.rpc("validators")[jss::result];
            BEAST_EXPECT(jrrUpdated[jss::NegativeUNL].isNull());
        }
    }

    void
    testDynamicUNL()
    {
        using namespace test::jtx;

        auto toStr = [](PublicKey const& publicKey) {
            return toBase58(TokenType::NodePublic, publicKey);
        };

        // Validator keys that will be in the published list
        std::vector<Validator> validators = {
            TrustedPublisherServer::randomValidator(),
            TrustedPublisherServer::randomValidator()};
        std::set<std::string> expectedKeys;
        for (auto const& val : validators)
            expectedKeys.insert(toStr(val.masterPublic));

        // Manage single-thread io_context for server.
        BasicApp worker{1};
        using namespace std::chrono_literals;
        NetClock::time_point const validUntil{3600s};
        NetClock::time_point const validFrom2{validUntil - 60s};
        NetClock::time_point const validUntil2{validFrom2 + 3600s};
        auto server = make_TrustedPublisherServer(
            worker.get_io_context(),
            validators,
            validUntil,
            {{validFrom2, validUntil2}},
            false,
            1,
            false);

        //----------------------------------------------------------------------
        // Publisher list site unavailable v1
        {
            // Publisher site information
            using namespace std::string_literals;
            std::string siteURI =
                "http://"s + getEnvLocalhostAddr() + ":1234/validators";

            Env env{
                *this,
                envconfig([&](std::unique_ptr<Config> cfg) {
                    cfg->section(SECTION_VALIDATOR_LIST_SITES).append(siteURI);
                    cfg->section(SECTION_VALIDATOR_LIST_KEYS)
                        .append(strHex(server->publisherPublic()));
                    return cfg;
                }),
            };

            env.app().validatorSites().start();
            env.app().validatorSites().join();

            {
                auto const jrr = env.rpc("server_info")[jss::result];
                BEAST_EXPECT(
                    jrr[jss::info][jss::validator_list][jss::expiration] ==
                    "unknown");
            }
            {
                auto const jrr = env.rpc("server_state")[jss::result];
                BEAST_EXPECT(
                    jrr[jss::state][jss::validator_list_expires].asInt() == 0);
            }
            {
                auto const jrr = env.rpc("validators")[jss::result];
                BEAST_EXPECT(
                    jrr[jss::validation_quorum].asUInt() ==
                    std::numeric_limits<std::uint32_t>::max());
                BEAST_EXPECT(jrr[jss::local_static_keys].size() == 0);
                BEAST_EXPECT(jrr[jss::trusted_validator_keys].size() == 0);
                BEAST_EXPECT(
                    jrr[jss::validator_list][jss::expiration] == "unknown");

                if (BEAST_EXPECT(jrr[jss::publisher_lists].size() == 1))
                {
                    auto jp = jrr[jss::publisher_lists][0u];
                    BEAST_EXPECT(jp[jss::available] == false);
                    BEAST_EXPECT(jp[jss::list].size() == 0);
                    BEAST_EXPECT(!jp.isMember(jss::seq));
                    BEAST_EXPECT(!jp.isMember(jss::expiration));
                    BEAST_EXPECT(!jp.isMember(jss::version));
                    BEAST_EXPECT(
                        jp[jss::pubkey_publisher] ==
                        strHex(server->publisherPublic()));
                }
                BEAST_EXPECT(jrr[jss::signing_keys].size() == 0);
            }
            {
                auto const jrr = env.rpc("validator_list_sites")[jss::result];
                if (BEAST_EXPECT(jrr[jss::validator_sites].size() == 1))
                {
                    auto js = jrr[jss::validator_sites][0u];
                    BEAST_EXPECT(js[jss::refresh_interval_min].asUInt() == 5);
                    BEAST_EXPECT(js[jss::uri] == siteURI);
                    BEAST_EXPECT(js.isMember(jss::last_refresh_time));
                    BEAST_EXPECT(js[jss::last_refresh_status] == "invalid");
                }
            }
        }
        // Publisher list site unavailable v2
        {
            // Publisher site information
            using namespace std::string_literals;
            std::string siteURI =
                "http://"s + getEnvLocalhostAddr() + ":1234/validators2";

            Env env{
                *this,
                envconfig([&](std::unique_ptr<Config> cfg) {
                    cfg->section(SECTION_VALIDATOR_LIST_SITES).append(siteURI);
                    cfg->section(SECTION_VALIDATOR_LIST_KEYS)
                        .append(strHex(server->publisherPublic()));
                    return cfg;
                }),
            };

            env.app().validatorSites().start();
            env.app().validatorSites().join();

            {
                auto const jrr = env.rpc("server_info")[jss::result];
                BEAST_EXPECT(
                    jrr[jss::info][jss::validator_list][jss::expiration] ==
                    "unknown");
            }
            {
                auto const jrr = env.rpc("server_state")[jss::result];
                BEAST_EXPECT(
                    jrr[jss::state][jss::validator_list_expires].asInt() == 0);
            }
            {
                auto const jrr = env.rpc("validators")[jss::result];
                BEAST_EXPECT(
                    jrr[jss::validation_quorum].asUInt() ==
                    std::numeric_limits<std::uint32_t>::max());
                BEAST_EXPECT(jrr[jss::local_static_keys].size() == 0);
                BEAST_EXPECT(jrr[jss::trusted_validator_keys].size() == 0);
                BEAST_EXPECT(
                    jrr[jss::validator_list][jss::expiration] == "unknown");

                if (BEAST_EXPECT(jrr[jss::publisher_lists].size() == 1))
                {
                    auto jp = jrr[jss::publisher_lists][0u];
                    BEAST_EXPECT(jp[jss::available] == false);
                    BEAST_EXPECT(jp[jss::list].size() == 0);
                    BEAST_EXPECT(!jp.isMember(jss::seq));
                    BEAST_EXPECT(!jp.isMember(jss::expiration));
                    BEAST_EXPECT(!jp.isMember(jss::version));
                    BEAST_EXPECT(
                        jp[jss::pubkey_publisher] ==
                        strHex(server->publisherPublic()));
                }
                BEAST_EXPECT(jrr[jss::signing_keys].size() == 0);
            }
            {
                auto const jrr = env.rpc("validator_list_sites")[jss::result];
                if (BEAST_EXPECT(jrr[jss::validator_sites].size() == 1))
                {
                    auto js = jrr[jss::validator_sites][0u];
                    BEAST_EXPECT(js[jss::refresh_interval_min].asUInt() == 5);
                    BEAST_EXPECT(js[jss::uri] == siteURI);
                    BEAST_EXPECT(js.isMember(jss::last_refresh_time));
                    BEAST_EXPECT(js[jss::last_refresh_status] == "invalid");
                }
            }
        }
        //----------------------------------------------------------------------
        // Publisher list site available
        server->start();
        // Publisher list site available v1
        {
            std::stringstream uri;
            uri << "http://" << server->local_endpoint() << "/validators";
            auto siteURI = uri.str();

            Env env{
                *this,
                envconfig([&](std::unique_ptr<Config> cfg) {
                    cfg->section(SECTION_VALIDATOR_LIST_SITES).append(siteURI);
                    cfg->section(SECTION_VALIDATOR_LIST_KEYS)
                        .append(strHex(server->publisherPublic()));
                    return cfg;
                }),
            };

            env.app().validatorSites().start();
            env.app().validatorSites().join();
            hash_set<NodeID> startKeys;
            for (auto const& val : validators)
                startKeys.insert(calcNodeID(val.masterPublic));

            env.app().validators().updateTrusted(
                startKeys,
                env.timeKeeper().now(),
                env.app().getOPs(),
                env.app().overlay(),
                env.app().getHashRouter());

            {
                auto const jrr = env.rpc("server_info")[jss::result];
                BEAST_EXPECT(
                    jrr[jss::info][jss::validator_list][jss::expiration] ==
                    to_string(validUntil));
            }
            {
                auto const jrr = env.rpc("server_state")[jss::result];
                BEAST_EXPECT(
                    jrr[jss::state][jss::validator_list_expires].asUInt() ==
                    validUntil.time_since_epoch().count());
            }
            {
                auto const jrr = env.rpc("validators")[jss::result];
                BEAST_EXPECT(jrr[jss::validation_quorum].asUInt() == 2);
                BEAST_EXPECT(
                    jrr[jss::validator_list][jss::expiration] ==
                    to_string(validUntil));
                BEAST_EXPECT(jrr[jss::local_static_keys].size() == 0);

                BEAST_EXPECT(
                    jrr[jss::trusted_validator_keys].size() ==
                    expectedKeys.size());
                for (auto const& jKey : jrr[jss::trusted_validator_keys])
                {
                    BEAST_EXPECT(expectedKeys.count(jKey.asString()) == 1);
                }

                if (BEAST_EXPECT(jrr[jss::publisher_lists].size() == 1))
                {
                    auto jp = jrr[jss::publisher_lists][0u];
                    BEAST_EXPECT(jp[jss::available] == true);
                    if (BEAST_EXPECT(jp[jss::list].size() == 2))
                    {
                        // check entries
                        std::set<std::string> foundKeys;
                        for (auto const& k : jp[jss::list])
                        {
                            foundKeys.insert(k.asString());
                        }
                        BEAST_EXPECT(foundKeys == expectedKeys);
                    }
                    BEAST_EXPECT(jp[jss::seq].asUInt() == 1);
                    BEAST_EXPECT(
                        jp[jss::pubkey_publisher] ==
                        strHex(server->publisherPublic()));
                    BEAST_EXPECT(jp[jss::expiration] == to_string(validUntil));
                    BEAST_EXPECT(jp[jss::version] == 1);
                }
                auto jsk = jrr[jss::signing_keys];
                BEAST_EXPECT(jsk.size() == 2);
                for (auto const& val : validators)
                {
                    BEAST_EXPECT(jsk.isMember(toStr(val.masterPublic)));
                    BEAST_EXPECT(
                        jsk[toStr(val.masterPublic)] ==
                        toStr(val.signingPublic));
                }
            }
            {
                auto const jrr = env.rpc("validator_list_sites")[jss::result];
                if (BEAST_EXPECT(jrr[jss::validator_sites].size() == 1))
                {
                    auto js = jrr[jss::validator_sites][0u];
                    BEAST_EXPECT(js[jss::refresh_interval_min].asUInt() == 5);
                    BEAST_EXPECT(js[jss::uri] == siteURI);
                    BEAST_EXPECT(js[jss::last_refresh_status] == "accepted");
                    // The actual time of the update will vary run to run, so
                    // just verify the time is there
                    BEAST_EXPECT(js.isMember(jss::last_refresh_time));
                }
            }
        }
        // Publisher list site available v2
        {
            std::stringstream uri;
            uri << "http://" << server->local_endpoint() << "/validators2";
            auto siteURI = uri.str();

            Env env{
                *this,
                envconfig([&](std::unique_ptr<Config> cfg) {
                    cfg->section(SECTION_VALIDATOR_LIST_SITES).append(siteURI);
                    cfg->section(SECTION_VALIDATOR_LIST_KEYS)
                        .append(strHex(server->publisherPublic()));
                    return cfg;
                }),
            };

            env.app().validatorSites().start();
            env.app().validatorSites().join();
            hash_set<NodeID> startKeys;
            for (auto const& val : validators)
                startKeys.insert(calcNodeID(val.masterPublic));

            env.app().validators().updateTrusted(
                startKeys,
                env.timeKeeper().now(),
                env.app().getOPs(),
                env.app().overlay(),
                env.app().getHashRouter());

            {
                auto const jrr = env.rpc("server_info")[jss::result];
                BEAST_EXPECT(
                    jrr[jss::info][jss::validator_list][jss::expiration] ==
                    to_string(validUntil2));
            }
            {
                auto const jrr = env.rpc("server_state")[jss::result];
                BEAST_EXPECT(
                    jrr[jss::state][jss::validator_list_expires].asUInt() ==
                    validUntil2.time_since_epoch().count());
            }
            {
                auto const jrr = env.rpc("validators")[jss::result];
                BEAST_EXPECT(jrr[jss::validation_quorum].asUInt() == 2);
                BEAST_EXPECT(
                    jrr[jss::validator_list][jss::expiration] ==
                    to_string(validUntil2));
                BEAST_EXPECT(jrr[jss::local_static_keys].size() == 0);

                BEAST_EXPECT(
                    jrr[jss::trusted_validator_keys].size() ==
                    expectedKeys.size());
                for (auto const& jKey : jrr[jss::trusted_validator_keys])
                {
                    BEAST_EXPECT(expectedKeys.count(jKey.asString()) == 1);
                }

                if (BEAST_EXPECT(jrr[jss::publisher_lists].size() == 1))
                {
                    auto jp = jrr[jss::publisher_lists][0u];
                    BEAST_EXPECT(jp[jss::available] == true);
                    if (BEAST_EXPECT(jp[jss::list].size() == 2))
                    {
                        // check entries
                        std::set<std::string> foundKeys;
                        for (auto const& k : jp[jss::list])
                        {
                            foundKeys.insert(k.asString());
                        }
                        BEAST_EXPECT(foundKeys == expectedKeys);
                    }
                    BEAST_EXPECT(jp[jss::seq].asUInt() == 1);
                    BEAST_EXPECT(
                        jp[jss::pubkey_publisher] ==
                        strHex(server->publisherPublic()));
                    BEAST_EXPECT(jp[jss::expiration] == to_string(validUntil));
                    BEAST_EXPECT(jp[jss::version] == 2);
                    if (BEAST_EXPECT(jp.isMember(jss::remaining)) &&
                        BEAST_EXPECT(jp[jss::remaining].isArray()) &&
                        BEAST_EXPECT(jp[jss::remaining].size() == 1))
                    {
                        auto const& r = jp[jss::remaining][0u];
                        if (BEAST_EXPECT(r[jss::list].size() == 2))
                        {
                            // check entries
                            std::set<std::string> foundKeys;
                            for (auto const& k : r[jss::list])
                            {
                                foundKeys.insert(k.asString());
                            }
                            BEAST_EXPECT(foundKeys == expectedKeys);
                        }
                        BEAST_EXPECT(r[jss::seq].asUInt() == 2);
                        BEAST_EXPECT(
                            r[jss::effective] == to_string(validFrom2));
                        BEAST_EXPECT(
                            r[jss::expiration] == to_string(validUntil2));
                    }
                }
                auto jsk = jrr[jss::signing_keys];
                BEAST_EXPECT(jsk.size() == 2);
                for (auto const& val : validators)
                {
                    BEAST_EXPECT(jsk.isMember(toStr(val.masterPublic)));
                    BEAST_EXPECT(
                        jsk[toStr(val.masterPublic)] ==
                        toStr(val.signingPublic));
                }
            }
            {
                auto const jrr = env.rpc("validator_list_sites")[jss::result];
                if (BEAST_EXPECT(jrr[jss::validator_sites].size() == 1))
                {
                    auto js = jrr[jss::validator_sites][0u];
                    BEAST_EXPECT(js[jss::refresh_interval_min].asUInt() == 5);
                    BEAST_EXPECT(js[jss::uri] == siteURI);
                    BEAST_EXPECT(js[jss::last_refresh_status] == "accepted");
                    // The actual time of the update will vary run to run, so
                    // just verify the time is there
                    BEAST_EXPECT(js.isMember(jss::last_refresh_time));
                }
            }
        }
    }

    void
    test_validation_create()
    {
        using namespace test::jtx;
        Env env{*this};
        auto result = env.rpc("validation_create");
        BEAST_EXPECT(
            result.isMember(jss::result) &&
            result[jss::result][jss::status] == "success");
        result = env.rpc(
            "validation_create",
            "BAWL MAN JADE MOON DOVE GEM SON NOW HAD ADEN GLOW TIRE");
        BEAST_EXPECT(
            result.isMember(jss::result) &&
            result[jss::result][jss::status] == "success");
    }

    void
    run() override
    {
        testPrivileges();
        testStaticUNL();
        testDynamicUNL();
        test_validation_create();
    }
};

BEAST_DEFINE_TESTSUITE(ValidatorRPC, rpc, ripple);

}  // namespace test
}  // namespace ripple
